import os
import json
import sys
from pptx import Presentation
from pptx.util import Pt, Emu
from pptx.enum.shapes import MSO_SHAPE, MSO_AUTO_SHAPE_TYPE
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_VERTICAL_ANCHOR
from pptx.enum.dml import MSO_LINE_DASH_STYLE as MSO_LINE_DASH
from pptx.shapes.picture import Picture

# 颜色字符串转RGB
_DEF_COLOR = {
    'B7472A': (183, 71, 42),
    'F3F3F3': (243, 243, 243),
    'E6E6E6': (230, 230, 230),
    'C00000': (192, 0, 0),
    '000000': (0, 0, 0),       # 黑色
    'FFFFFF': (255, 255, 255), # 白色
    '4472C4': (68, 114, 196),  # 蓝色ACCENT5
    '70AD47': (112, 173, 71),  # 绿色ACCENT6
    'ED7D31': (237, 125, 49),  # 橙色ACCENT2
    'A5A5A5': (165, 165, 165), # 灰色ACCENT3
    'FFC000': (255, 192, 0),   # 黄色ACCENT4
    '5B9BD5': (91, 155, 213),  # 浅蓝色ACCENT1
}

def parse_rgb(color_str):
    if not color_str:
        return (0, 0, 0)  # 默认黑色
    color_str = str(color_str).replace('#', '').upper()
    # 新增：支持scheme名称
    if color_str in ['TX1', 'TEXT1']:
        return (0, 0, 0)  # 主题色tx1通常为黑色
    if color_str in ['BG1', 'BACKGROUND1']:
        return (255, 255, 255)  # 主题色bg1通常为白色
    if color_str in ['TX2', 'TEXT2']:
        return (128, 128, 128)  # 主题色tx2一般为灰色
    if color_str in ['BG2', 'BACKGROUND2']:
        return (238, 238, 238)  # 主题色bg2一般为浅灰
    if len(color_str) == 6 and all(x in '0123456789ABCDEF' for x in color_str):
        try:
            return tuple(int(color_str[i:i+2], 16) for i in (0, 2, 4))
        except Exception:
            return (0, 0, 0)
    return (0, 0, 0)

def map_alignment(align_str):
    if not align_str:
        return None
    if 'CENTER' in align_str.upper():
        return PP_ALIGN.CENTER
    if 'LEFT' in align_str.upper():
        return PP_ALIGN.LEFT
    if 'RIGHT' in align_str.upper():
        return PP_ALIGN.RIGHT
    if 'JUSTIFY' in align_str.upper():
        return PP_ALIGN.JUSTIFY
    return None

def map_vertical_anchor(anchor_str):
    if not anchor_str:
        return None
    if 'MIDDLE' in anchor_str.upper() or 'CENTER' in anchor_str.upper():
        return MSO_VERTICAL_ANCHOR.MIDDLE
    if 'BOTTOM' in anchor_str.upper():
        return MSO_VERTICAL_ANCHOR.BOTTOM
    if 'TOP' in anchor_str.upper():
        return MSO_VERTICAL_ANCHOR.TOP
    return None

def scheme_color_fallback(scheme_str):
    # 主题色/背景色兜底映射
    if not scheme_str:
        return (255,255,255) # 默认白色
    if 'BACKGROUND' in scheme_str.upper():
        return (255,255,255)
    if 'TEXT_1' in scheme_str.upper():
        return (0,0,0)
    if 'TEXT_2' in scheme_str.upper():
        return (128,128,128)
    if 'ACCENT1' in scheme_str.upper():
        return (91, 155, 213)  # 浅蓝色
    if 'ACCENT2' in scheme_str.upper():
        return (237, 125, 49)  # 橙色 
    if 'ACCENT3' in scheme_str.upper():
        return (165, 165, 165) # 灰色
    if 'ACCENT4' in scheme_str.upper():
        return (255, 192, 0)   # 黄色  
    if 'ACCENT5' in scheme_str.upper():
        return (68, 114, 196)  # 蓝色
    if 'ACCENT6' in scheme_str.upper():
        return (112, 173, 71)  # 绿色
    if 'DARK1' in scheme_str.upper():
        return (0, 0, 0)       # 黑色
    if 'LIGHT1' in scheme_str.upper():
        return (255, 255, 255) # 白色
    return (255,255,255)

def set_shape_style(shape, shape_info):
    # Picture类型不支持fill/line等Shape属性
    if isinstance(shape, Picture):
        # 只设置通用属性
        shape.name = shape_info.get('name', '')
        return
    # 其它形状按原逻辑设置
    shape.name = shape_info.get('name', '')
    # 填充色
    fill = shape_info.get('fill')
    # 兼容 [color, type] 结构
    if isinstance(fill, (list, tuple)) and len(fill) > 0:
        fill_color = fill[0]
    elif isinstance(fill, dict) and fill.get('color'):
        fill_color = fill['color']
    else:
        fill_color = fill if isinstance(fill, str) else None
    if not fill_color:
        shape.fill.solid()
        shape.fill.background()
    else:
        rgb = parse_rgb(fill_color)
        shape.fill.solid()
        shape.fill.fore_color.rgb = RGBColor(*rgb)
    # 边框
    line = shape_info.get('line')
    line_color = None
    if line and line.get('color'):
        line_color = line['color']
    if not line_color:
        shape.line.fill.background()
        shape.line.width = 0
    else:
        rgb = parse_rgb(line_color)
        shape.line.color.rgb = RGBColor(*rgb)
        line_width = shape_info.get('line_width')
        line_dash = shape_info.get('line_dash')
        if line_width is not None:
            shape.line.width = line_width
        if line_dash is not None:
            shape.line.dash_style = line_dash
    # 透明度
    if shape_info.get('fill_transparency'):
        try:
            shape.fill.transparency = float(shape_info['fill_transparency'])
        except Exception:
            pass
    # 禁用主题/母版阴影
    try:
        shape.shadow.inherit = False
    except Exception:
        pass
    # 旋转
    if shape_info.get('rotation'):
        shape.rotation = float(shape_info['rotation'])

def add_text_to_shape(shape, info):
    runs = info['runs']
    tf = shape.text_frame
    tf.clear()
    if 'alignment' in info and info['alignment']:
        align = map_alignment(info['alignment'])
        if align:
            tf.paragraphs[0].alignment = align
    if 'vertical_anchor' in info and info['vertical_anchor']:
        va = map_vertical_anchor(info['vertical_anchor'])
        if va:
            tf.vertical_anchor = va
    if 'wrap' in info:
        tf.word_wrap = info['wrap']
    if 'margins' in info:
        m = info['margins']
        tf.margin_left = m.get('left', 0)
        tf.margin_right = m.get('right', 0)
        tf.margin_top = m.get('top', 0)
        tf.margin_bottom = m.get('bottom', 0)
    p = tf.paragraphs[0]
    for run in runs:
        r = p.add_run()
        r.text = run['text']
        if run.get('font') and run['font'] != 'None':
            r.font.name = run['font']
        if run.get('size'):
            sz = run['size']
            if sz and isinstance(sz, int) and sz > 1000:
                r.font.size = Pt(sz / 12700)
            elif sz:
                r.font.size = Pt(sz)
        c = run.get('color')
        if c and isinstance(c, str) and len(c) == 6 and all(x in '0123456789ABCDEFabcdef' for x in c):
            rgb = parse_rgb(c)
            r.font.color.rgb = RGBColor(*rgb)

def get_shape_enum(shape_type):
    # 支持int、str、MSO_SHAPE/MSO_AUTO_SHAPE_TYPE
    if isinstance(shape_type, int):
        return shape_type
    if isinstance(shape_type, MSO_SHAPE) or isinstance(shape_type, MSO_AUTO_SHAPE_TYPE):
        return shape_type
    
    # 转为字符串并标准化处理
    shape_type_str = str(shape_type).upper()
    
    # 删除所有针对椭圆、矩形等的特殊处理，仅通用映射
    try:
        if hasattr(MSO_AUTO_SHAPE_TYPE, shape_type_str):
            return getattr(MSO_AUTO_SHAPE_TYPE, shape_type_str)
        if hasattr(MSO_SHAPE, shape_type_str):
            return getattr(MSO_SHAPE, shape_type_str)
        
        # 兼容字符串如'ShapeType.RECTANGLE'等
        if '.' in shape_type_str:
            key = shape_type_str.split('.')[-1]
            if hasattr(MSO_AUTO_SHAPE_TYPE, key):
                return getattr(MSO_AUTO_SHAPE_TYPE, key)
            if hasattr(MSO_SHAPE, key):
                return getattr(MSO_SHAPE, key)
        
        # 提取数字，如"AUTO_SHAPE (1)"或"OVAL (9)"
        import re
        if '(' in shape_type_str and ')' in shape_type_str:
            match = re.search(r'\((\d+)\)', shape_type_str)
            if match:
                type_num = int(match.group(1))
                # 尝试通用映射
                for attr in dir(MSO_AUTO_SHAPE_TYPE):
                    if not attr.startswith('_'):
                        try:
                            if getattr(MSO_AUTO_SHAPE_TYPE, attr) == type_num:
                                return getattr(MSO_AUTO_SHAPE_TYPE, attr)
                        except:
                            pass
    except Exception as e:
        print(f"形状类型解析异常: {e} - 使用默认矩形")
    
    # 调试输出
    print(f"警告: 未识别的形状类型 '{shape_type}' - 使用默认矩形")
    
    # 默认矩形
    return MSO_AUTO_SHAPE_TYPE.RECTANGLE

def add_shape_recursive(parent_shapes, shape_info, text_info_map, parent_offset=(0,0), images_dir='images', level=0, base_dir=None):
    name = shape_info.get('name', '')
    left = int(shape_info['left'])
    top = int(shape_info['top'])
    width = int(shape_info['width'])
    height = int(shape_info['height'])
    # 递归累加parent_offset
    abs_left, abs_top = left + parent_offset[0], top + parent_offset[1]
    print(f'[调试] {name} 递归坐标, left={left}, top={top}, parent_offset={parent_offset}, abs_left={abs_left}, abs_top={abs_top}, width={width}, height={height}')
    shape_type = get_shape_enum(shape_info.get('shape_type', 1))
    # 图片元素还原
    if (shape_info.get('shape_type') == 'PICTURE' or shape_info.get('type') == 'image' or shape_info.get('image_path')) and shape_info.get('image_path'):
        img_path = shape_info['image_path']
        if not os.path.isabs(img_path):
            if base_dir:
                img_path = os.path.join(base_dir, img_path.replace('/', os.sep))
            else:
                img_path = os.path.join(images_dir, os.path.basename(img_path))
        print(f'[调试] 还原图片: name={name}, image_path={img_path}, left={abs_left}, top={abs_top}, width={width}, height={height}')
        if not os.path.exists(img_path):
            print(f"图片文件不存在: {img_path}")
            return None
        shape = parent_shapes.add_picture(img_path, Emu(abs_left), Emu(abs_top), Emu(width), Emu(height))
        shape.name = name
        set_shape_style(shape, shape_info)
        return shape
    # 组合
    if 'children' in shape_info and shape_info['children']:
        print(f'[调试] 进入组合: {name}, children数量: {len(shape_info["children"])}')
        group = parent_shapes.add_group_shape()
        group.name = name
        group.left = Emu(abs_left)
        group.top = Emu(abs_top)
        group.width = Emu(width)
        group.height = Emu(height)
        new_offset = (abs_left, abs_top)
        for child in shape_info['children']:
            add_shape_recursive(group.shapes, child, text_info_map, parent_offset=new_offset, images_dir=images_dir, level=level+1, base_dir=base_dir)
        return group
    else:
        print(f'[调试] 还原普通形状: name={name}, left={abs_left}, top={abs_top}, width={width}, height={height}, shape_type={shape_type}')
        shape = parent_shapes.add_shape(shape_type, Emu(abs_left), Emu(abs_top), Emu(width), Emu(height))
        shape.name = name
        set_shape_style(shape, shape_info)
        if 'runs' in shape_info:
            add_text_to_shape(shape, shape_info)
        elif name in text_info_map:
            add_text_to_shape(shape, text_info_map[name])
        return shape

def get_latest_output_dir():
    output_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '../output'))
    # 获取所有output子目录
    all_dirs = [d for d in os.listdir(output_root) if os.path.isdir(os.path.join(output_root, d)) and d.startswith('游戏素材_第6页_')]
    # 按照时间戳排序，获取最新的目录
    if all_dirs:
        latest_dir = sorted(all_dirs, reverse=True)[0]  # 按名称倒序排列，第一个是最新的
        return os.path.join(output_root, latest_dir)
    return None

def process_elements(slide, elements, parent_group=None, level=0, base_dir=None):
    print(f'[调试] process_elements(level={level}) 元素数量: {len(elements)}, 类型: {[type(e) for e in elements]}')
    for idx, e in enumerate(elements):
        print(f'[调试] 元素{idx}类型: {type(e)}, 内容: {str(e)[:200]}')
    def get_z(shape):
        return shape.get('z_order', 0) if isinstance(shape, dict) and shape.get('z_order') is not None else 0
    sorted_elements = sorted([e for e in elements if isinstance(e, dict)], key=get_z)
    for elem in sorted_elements:
        add_shape_recursive(slide.shapes if parent_group is None else parent_group, elem, {}, level=level+1, base_dir=base_dir)

def main():
    import argparse
    parser = argparse.ArgumentParser(description="根据解析结果还原PPTX文件")
    parser.add_argument('--folder', '-f', required=True, help='解析结果输出文件夹路径')
    args = parser.parse_args()
    base_dir = os.path.abspath(args.folder)
    print(f"使用输出目录: {base_dir}")
    layouts_dir = os.path.join(base_dir, 'layouts')
    if not os.path.exists(layouts_dir):
        print(f"错误：{layouts_dir} 不存在")
        return
    # 自动查找布局json
    layout_files = [f for f in os.listdir(layouts_dir) if f.endswith('_布局.json')]
    if not layout_files:
        print(f"错误：{layouts_dir} 下未找到布局json文件")
        return
    layout_file = os.path.join(layouts_dir, layout_files[0])
    print(f"读取布局文件: {layout_file}")
    with open(layout_file, 'r', encoding='utf-8') as f:
        layout_data = json.load(f)
    print('[调试] layout_data:', json.dumps(layout_data, ensure_ascii=False, indent=2))
    elements = layout_data['layout'] if 'layout' in layout_data else layout_data['elements']
    print('[调试] elements类型:', type(elements), '元素数量:', len(elements) if isinstance(elements, list) else 'N/A')
    if isinstance(elements, list):
        for idx, e in enumerate(elements):
            print(f'[调试] 元素{idx}类型: {type(e)}, keys: {list(e.keys()) if isinstance(e, dict) else e}')
    prs = Presentation()
    prs.slide_width = layout_data.get('slide_width', 9144000)
    prs.slide_height = layout_data.get('slide_height', 5143500)
    slide_layout = prs.slide_layouts[6]
    slide = prs.slides.add_slide(slide_layout)
    process_elements(slide, elements, None, base_dir=base_dir)
    pptx_path = os.path.join(base_dir, 'output.pptx')
    prs.save(pptx_path)
    print(f"PPTX已生成: {pptx_path}")

if __name__ == '__main__':
    main() 